﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Drawing;
using Axe.Collections;
using System.Xml.Serialization;

namespace Axe.Geometry
{
    [Serializable]
    [TypeConverter(typeof(CollectionConverter))]
    [XmlRoot("Vector2Collection")]
    public class Vector2Collection : EachCollection<Vector2>, IEquatable<Vector2Collection>
    {
        #region public PointF[] ToArray()
        public PointF[] ToArray()
        {
            PointF[] points = new PointF[Count];

            for (int i = 0; i < Count; i++)
            {
                Vector2 v = this[i];
                points[i] = new PointF((float)v.X, (float)v.Y);
            }

            return points;
        } 
        #endregion

        #region private static int CompareDelegate(KeyValuePair<int, double> left, KeyValuePair<int, double> right)
        private static int CompareDelegate(KeyValuePair<int, double> left, KeyValuePair<int, double> right)
        {
            return left.Value.CompareTo(right.Value);
        }
        #endregion

        #region public bool FindNearestPoints(Vector2 p, out int leftIdx, out int rightIdx)
        public bool FindNearestPoints(Vector2 p, out int leftIdx, out int rightIdx)
        {
            leftIdx = -1;
            rightIdx = -1;
            if (Count < 2)
                return false;

            Pair<Vector2, Vector2> bounds = GetBoundRect();
            Vector2 center = (bounds.Left + bounds.Right) / 2.0;

            List<KeyValuePair<int, double>> sortedItems = new List<KeyValuePair<int, double>>();

            // Строим список точек
            for (int i = 0; i < Count; i++)
            {
                double dist = Vector2.Distance(p, this[i]);
                KeyValuePair<int, double> item = new KeyValuePair<int, double>(i, dist);
                sortedItems.Add(item);
            }

            // Сортируем по удалению от целевой точки
            sortedItems.Sort(CompareDelegate);

            int bIndex = sortedItems[0].Key;
            Vector2 lcVector = this[bIndex] - center;
            Vector2 cpVector = p - center;
            bool isCw1 = (Vector2.Cross(lcVector, cpVector) >= 0);

            for (int i = 1; i < sortedItems.Count; i++)
            {
                int cIndex = sortedItems[i].Key;
                Vector2 cvVector = this[cIndex] - center;
                bool isCw2;

                isCw2 = (Vector2.Cross(cpVector, cvVector) >= 0);

                if (isCw1 == isCw2)
                {
                    leftIdx = Math.Min(bIndex, cIndex);
                    rightIdx = Math.Max(bIndex, cIndex);
                    return true;
                }
            }

            return false;
        } 
        #endregion

        public Vector2Collection WieldPoint(float maximumDistance)
        {
            float distSqr = maximumDistance*maximumDistance;

            for (int i = 0; i < Count - 1; i++)
            {
                Vector2 v1 = this[i];
                Vector2 v2 = this[i + 1];

                if (Vector2.DistanceSquared(v1, v2) < distSqr)
                {
                    this[i] = (v1 + v2) / 2.0;
                    RemoveAt(i + 1);
                    i--;
                }
            }

            return this;
        }

        #region public bool IsPointInside(double x, double y)
        public bool IsPointInside(double x, double y)
        {
            return IsPointInside(Vector2.FromXY(x, y));
        } 
        #endregion

        #region private bool IsPointInside(Vector2 point, Vector2 center)
        private bool IsPointInside(Vector2 point, Vector2 center)
        {
            for (int i = 0, k = 0; i < Count; i++)
            {
                k = i + 1;
                if (k == Count)
                    k = 0;

                Vector2 a = this[i];
                Vector2 b = this[k];
                Vector2 cross = Vector2.GetLineCross(a, b, center, point);

                if (cross != null)
                    return false;
            }

            return true;
        } 
        #endregion

        #region public bool IsPointInside(Vector2 point)
        /// <summary>
        /// Определяет, находится-ли точка внутри замкнутой ломанной-профиля
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        public bool IsPointInside(Vector2 point)
        {
            Pair<Vector2,Vector2> bounds= GetBoundRect();
            Vector2 center = (bounds.Left + bounds.Right) / 2.0;
            return IsPointInside(point, center);
        }
        #endregion


        #region public Pair<Vector2, Vector2> GetBoundRect()
        public Pair<Vector2, Vector2> GetBoundRect()
        {
            Vector2 min = new Vector2(this[0]);
            Vector2 max = new Vector2(this[0]);

            for (int i = 0; i < Count; i++)
            {
                Vector2 v = this[i];

                max.X = Math.Max(max.X, v.X);
                max.Y = Math.Max(max.Y, v.Y);

                min.X = Math.Min(min.X, v.X);
                min.Y = Math.Min(min.Y, v.Y);
            }

            return new Pair<Vector2, Vector2>(min, max);
        } 
        #endregion

        #region public PointF[] ToArray(bool hitro)
        public PointF[] ToArray(bool hitro)
        {
            if (!hitro)
                return ToArray();

            List<PointF> pts = new List<PointF>();

            int count = this.Count;

            for (int i = 0; i < count; i++)
            {
                pts.Add((PointF)this[i]);
                pts.Add((PointF)this[(i + 1) % count]);
            }

            return pts.ToArray();
        } 
        #endregion

        #region public static bool HasIntersection(Vector2Collection a, Vector2Collection b)
        /// <summary>
        /// Проверка взаимного пересечения двух областей
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static bool HasIntersection(Vector2Collection a, Vector2Collection b)
        {
            for (int i = 0, k = 0; i < a.Count; i++)
            {
                k = (i + 1) % a.Count;

                Vector2 a1 = a[i];
                Vector2 b1 = a[k];

                for (int j = 0, m = 0; j < b.Count; j++)
                {
                    m = (j + 1) % b.Count;

                    Vector2 a2 = b[j];
                    Vector2 b2 = b[m];

                    Vector2 cross = Vector2.GetLineCross(a1, b1, a2, b2);

                    if (cross != null)
                        return true;
                }
            }

            return false;
        } 
        #endregion

        #region IEquatable<Vector2Collection> Members

        public bool Equals(Vector2Collection other)
        {
            if (this.Count != other.Count)
                return false;

            for (int i = 0; i < Count; i++)
            {
                if(!other[i].Equals(this[i]))
                    return false;
            }

            return true;
        }

        #endregion

        public Vector2Collection Clone()
        {
            Vector2Collection res = new Vector2Collection();

            for (int i = 0; i < Count; i++)
            {
                res.Add(this[i].Clone());
            }

            return res;
        }

        public const int EmptyPointIndex = -1;

        public int GetPointOverIndex(Vector2 point, double epsilon)
        {
            int count = this.Count;

            double epsSql = epsilon * epsilon;

            for (int i = 0; i < count; i++)
            {
                if (Vector2.DistanceSquared(point, this[i]) <= epsSql)
                {
                    return i;
                }
            }

            return EmptyPointIndex;
        }

        public Vector2 GetNearest(Vector2 vector2)
        {
            if(Count == 0)
                return null;
            if(Count == 1)
                return this[0];

            double distance = Vector2.DistanceSquared(vector2, this[0]);
            int index = 0;

            for (int i = 1; i < Count; i++)
            {
                double testDistance = Vector2.DistanceSquared(vector2, this[0]);
                if (testDistance < distance)
                {
                    index = i;
                    distance = testDistance;
                }
            }

            return this[index];
        }

        public void AddRange(Point[] items)
        {
            foreach (Point p in items)
            {
                Add(p);
            }
        }

    }






    public class StringVector2Converter : TypeConverter
    {
        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            if (destinationType == typeof(string))
                return true;
            return base.CanConvertTo(context, destinationType);
        }

        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            if (sourceType == typeof(Vector2Collection))
                return true;
            if(sourceType == typeof(string))
                return true;
            return base.CanConvertFrom(context, sourceType);
        }

        public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
        {
            if (value is Vector2Collection)
            {
                return ToString(value as Vector2Collection);
            } 
            else if (value is string)
            {
                return ToCollection(value as string);
            }

            return base.ConvertFrom(context, culture, value);
        }

        public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
        {
            if (value is string && destinationType == typeof(Vector2Collection))
            {
                return ToCollection(value as string);
            }
            else if (value is Vector2Collection)
            {
                return ToString(value as Vector2Collection);
            }

            return base.ConvertTo(context, culture, value, destinationType);
        }

        public const char Separator = '|';

        public static Vector2Collection ToCollection(string data)
        {
            data = data.Trim().Replace(Environment.NewLine, "");
                
            Vector2Collection v2Coll = new Vector2Collection();

            if (String.IsNullOrEmpty(data))
                return v2Coll;

            string[] items = data.Split(Separator);

            foreach (string item in items)
            {
                Vector2 vector = Vector2.Parse(item);
                if (vector != null)
                {
                    v2Coll.Add(vector);
                }
            }

            return v2Coll;
        }

        public static string ToString(Vector2Collection collection)
        {
            StringBuilder sb = new StringBuilder();

            foreach (Vector2 v in collection)
            {
                sb.Append(v.ToString());
                sb.Append(Separator);
            }

            if (sb.Length > 0)
                sb.Remove(sb.Length - 1, 1);

            return sb.ToString();
        }
    }
}
